package cn.xinfei.xdecision.engine.runner.service;

import cn.xinfei.xdecision.common.model.enginex.po.EngineVersion;
import cn.xinfei.xdecision.common.utils.util.JsonUtils;
import cn.xinfei.xdecision.constant.EngineResultEnum;
import cn.xinfei.xdecision.constant.GlobalConstant;
import cn.xinfei.xdecision.nacos.NacosSwitchProperties;
import cn.xinfei.xdecision.engine.runner.context.PipelineContext;
import cn.xinfei.xdecision.engine.runner.context.PipelineContextHolder;
import cn.xinfei.xdecision.engine.runner.context.XDecisionContext;
import cn.xinfei.xdecision.engine.runner.context.model.NodeContextModel;
import cn.xinfei.xdecision.engine.runner.datax.DataPreCollectHandler;
import cn.xinfei.xdecision.engine.runner.datax.RiskDataApiAdapter;
import cn.xinfei.xdecision.engine.runner.executor.DecisionFlowPipeline;
import cn.xinfei.xdecision.engine.runner.executor.NodeHandler;
import cn.xinfei.xdecision.engine.runner.executor.model.StateTransmitModel;
import cn.xinfei.xdecision.engine.runner.metadata.MetadataProvider;
import cn.xinfei.xdecision.engine.runner.vo.EngineApiResponse;
import com.dianping.cat.Cat;
import io.micrometer.core.instrument.MeterRegistry;
import io.micrometer.core.instrument.Metrics;
import io.micrometer.core.instrument.Tags;
import lombok.extern.slf4j.Slf4j;
import org.slf4j.MDC;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.util.*;

@Slf4j
@Service
public class EngineReentryService {

    @Autowired
    private EngineReentryLogService engineReentryLogService;

    @Autowired
    private DecisionResultDetailService decisionResultDetailService;

    @Autowired
    private MeterRegistry meterRegistry;

    @Autowired
    private MetadataProvider metadataProvider;

    @Autowired
    private NacosSwitchProperties nacosSwitchProperties;

    @Autowired
    private DataPreCollectHandler dataPreCollectHandler;


    private EngineApiResponse restoreNormalNode(String requestId, String decisionId, String prevDecisionId, String engineCode) throws Exception {
        MDC.put(GlobalConstant.TRACE_ID, requestId);
        MDC.put(GlobalConstant.PID, decisionId);
        if (!prevDecisionId.equals(decisionId)) {
            //如果跨决策流，则需要更新快照信息
            PipelineContext document = decisionResultDetailService.getDecisionResultDetailByDecisionId(requestId, decisionId, engineCode);
            if (document != null) {
                //此处不能完全用库里的快照信息还原，而应该是继承当前快照的一部分数据，类似子决策流

                XDecisionContext context = XDecisionContext.getContext();
                PipelineContext prevPipelineContext = context.getPipelineContext();

                context.setPipelineContext(document);
                PipelineContext nextPipelineContext = context.getPipelineContext();
                //继承上一个决策流的context
                nextPipelineContext.getContext().putAll(prevPipelineContext.getContext());
                //1.预加载变量，失败后不阻塞流程.异步的暂时不进行预加载
                //这个位置，可以优化为只加载当前新决策流的变量，因为上一个决策流执行时，已经被加载过了。此时重复加载，虽然会过滤掉已经加载过的，那些加载不到的，还会重试一次，或者加载了一些已经用不到的变量
                log.info("switch:" + nacosSwitchProperties);
                if (nacosSwitchProperties.isPreloadAsync()) {
                    Set<String> preload = DecisionFlowPipeline.preload();
                    Boolean result = dataPreCollectHandler.queryFieldValue(preload, RiskDataApiAdapter.CollectTypeEnum.PRE.toString());
                }

            } else {
                log.error("普通决策流中普通节点，重入失败，没有找到快照信息,requestId={}, decisionId={},engineCode={}", requestId, decisionId, engineCode);
                Metrics.counter("xengine_reentry_no_snapshot_total").increment();
            }
        }

        NodeContextModel nodeContextModel = PipelineContextHolder.popNodeContextModel();

        if (nodeContextModel == null) {
            log.warn("getNodeCodeStack is empty");
            EngineApiResponse execute = new EngineApiResponse(EngineResultEnum.NO_DATA_STACK);
            execute.setDecisionId(decisionId);
            execute.setEngineCode(engineCode);
            meterRegistry.counter("xengine_reentry_normal_stack_empty").increment();
//            engineReentryLogService.updateDecisionResult(execute, decisionId, decisionId, engineCode);
            return execute;
        }

        String nodeCode = nodeContextModel.getNodeCode();
        String reentryEngineCode = nodeContextModel.getEngineCode();
        String reentryDecisionId = nodeContextModel.getDecisionId();
        log.info("prevDecisionId={},decisionId={},reentryDecisionId={},reentryEngineCode={},nodeCode={}", prevDecisionId, decisionId, reentryDecisionId, reentryEngineCode, nodeCode);

        MDC.put(GlobalConstant.PID, reentryDecisionId);
        PipelineContextHolder.setDecisionId(reentryDecisionId);
        if (decisionId.equals(reentryDecisionId) == false) {
            log.warn("reentryDecisionId={} 与 decisionId={} 不相同", reentryDecisionId, decisionId);
        }

        EngineApiResponse currResponse = this.execute(reentryDecisionId, reentryEngineCode, nodeCode);
        log.info("reentry normal requestId={},decisionId={},code={},result={},state={}",
                currResponse.getRequestId(), currResponse.getDecisionId(), currResponse.getCode(), currResponse.getResult(), currResponse.getState());

        if (currResponse == null) {
            log.info("重入决策流执行异常,response is null,reentryDecisionId={},requestId={},reentryEngineCode={}", reentryDecisionId, requestId, reentryEngineCode);
            return currResponse;
        }
        String state = currResponse.getResult();
        String code = currResponse.getCode();
        //更新当前决策流的状态信息
        log.info("重入决策流执行结果：更新当前决策流的状态信息,reentryEngineCode={},state={},code={}", reentryEngineCode, state, code);
        engineReentryLogService.updateDecisionResult(currResponse, reentryDecisionId, reentryDecisionId, reentryEngineCode);

        if (EngineResultEnum.REJECT.getResult().equals(state)
                || EngineResultEnum.PENDING.getResult().equals(state)
                || EngineResultEnum.REVIEW.getResult().equals(state)
                || EngineResultEnum.EXCEPTION.getResult().equals(state)) {
            log.info("重入决策流执行结束：进入PENDING或REJECT,reentryEngineCode={},state={},code={}", reentryEngineCode, state, code);
            updatePreEngineContextState(currResponse, reentryDecisionId);
            return currResponse;
        }
        if (EngineResultEnum.NO_EXISTS.getCode().equalsIgnoreCase(code)) {
            //重入的时候不应该出现决策流不存在的情况，一般是在服务启动时，元数据尚未加载成功时，调度就触发了。todo xxl调度任务能不能延迟注册？
            meterRegistry.counter("xengine_reentry_normal_no_exists").increment();
            log.info("重入决策流执行结束：决策流不存在,reentryEngineCode={},state={},code={}", reentryEngineCode, state, code);
            return currResponse;
        }

        //取出当前决策记录的下一个节点
        nodeContextModel = PipelineContextHolder.peekNodeContextModel();
        if (nodeContextModel == null) {
            log.info("重入决策流执行结束：reentryEngineCode={},state={},code={}", reentryEngineCode, state, code);
            return currResponse;
        }

        //继承上个决策流的执行状态
        EngineResultEnum prevState = EngineResultEnum.getByCode(code);
        XDecisionContext context = XDecisionContext.getContext();
        context.setStateContext(StateTransmitModel.build().setEngineResultEnum(prevState));

        String nextDecisionId = nodeContextModel.getDecisionId();
        String nextEngineCode = nodeContextModel.getEngineCode();
        String nextNodeCode = nodeContextModel.getNodeCode();
        log.info("{} next决策流 {},{} 继承prev决策流 {} 的state={}", nextDecisionId, nextEngineCode, nextNodeCode, reentryEngineCode, prevState);

        return restoreNormalNode(requestId, nextDecisionId, reentryDecisionId, nextEngineCode);
    }

    public List<EngineApiResponse> reentry(String requestId, String decisionId, String engineCode, String scheduleModel, String ipAddress, String lockKey) {
        List<EngineApiResponse> responses = new ArrayList<>();
        try {
            XDecisionContext.destroy();

            EngineApiResponse execute = restoreNormalNode(requestId, decisionId, "-1", engineCode);
            log.info("============ 普通决策流中普通节点，接口重入 response：{}", JsonUtils.toJsonString(execute));
            Metrics.counter("xengine_reentry_result_total", Tags.of("result", execute.getResult())).increment();

        } catch (Exception e) {
            log.error("reentry exception.fail={}", e.getMessage(), e);
            Cat.logError(e);
            Metrics.counter("xengine_reentry_exception_total").increment();
        } finally {
            XDecisionContext.destroy();
        }

        return responses;

    }

    private void updatePreEngineContextState(EngineApiResponse currResponse, String executorDecisionId) {
        Stack<NodeContextModel> nodeContextModelStack = PipelineContextHolder.getNodeContextModel();
        for (int i = 0; i < nodeContextModelStack.size() && null != currResponse; i++) {
            //更新父决策流的状态信息
            NodeContextModel nodeContextModel = nodeContextModelStack.elementAt(i);
            String decisionId = nodeContextModel.getDecisionId();
            if (decisionId.equals(executorDecisionId)) {
                log.info("pre decisionId 等于当前，不需要更新.nodeContextModel={}", JsonUtils.toJsonString(nodeContextModel));
                continue;
            }
            String engineCode = nodeContextModel.getEngineCode();

            log.info("executorDecisionId={},update pre decisionId={},engineCode={}", executorDecisionId, decisionId, engineCode);
            engineReentryLogService.updateDecisionResult(currResponse, executorDecisionId, decisionId, engineCode);
        }
    }


    private EngineApiResponse execute(String decisionId, String reentryEngineCode, String nodeCode) {
        Long start = System.currentTimeMillis();
//        MDC.put(GlobalConstant.PID, decisionId);
        EngineApiResponse engineApiResponse = new EngineApiResponse();
        XDecisionContext context = XDecisionContext.getContext();
        engineApiResponse.setEngineCode(reentryEngineCode);
        Map<String, NodeHandler> engineNodeMap = metadataProvider.getEngineNodeList(reentryEngineCode);
        if (null == engineNodeMap || engineNodeMap.size() == 0) {
            log.warn("请求引擎不存在或尚未部署运行,reentryEngineCode={}", reentryEngineCode);
            engineApiResponse.setEngineResult(EngineResultEnum.NO_EXISTS);
            return engineApiResponse;
        }


        EngineVersion engineVersion = metadataProvider.getEngineVersion(reentryEngineCode);
        Long versionNo = engineVersion.getVersionNo();
        PipelineContextHolder.setEngineVersion(versionNo + "");
        PipelineContextHolder.setEngineName(engineVersion.getEngineName());
        PipelineContextHolder.setEngineCode(reentryEngineCode);

        //补充元数据到上下文中
        context.getPipelineMetaInfoContext().putAll(engineNodeMap);


        Map<String, NodeHandler> reentryEngineNodeMap = metadataProvider.getEngineNodeList(reentryEngineCode);
        if (null != reentryEngineNodeMap && reentryEngineNodeMap.size() > 0) {
            context.getPipelineMetaInfoContext().putAll(reentryEngineNodeMap);
        } else {
            log.error("重入的决策流节点信息不存在,reentryEngineCode={}", reentryEngineCode);
            engineApiResponse.setEngineResult(EngineResultEnum.NO_EXISTS);
            return engineApiResponse;
        }


        log.info("执行决策流节点开始,reentryEngineCode={},versionNo={}", reentryEngineCode, versionNo);

        String bizId = PipelineContextHolder.getRequestId();
        engineApiResponse.setRequestId(bizId);
        engineApiResponse.setDecisionId(decisionId);
        try {

            StateTransmitModel transmitModel = DecisionFlowPipeline.reentry(nodeCode);
            EngineResultEnum resultEnum = transmitModel.getEngineResultEnum();

            Long step1 = System.currentTimeMillis();
            long respond = step1 - start;
            log.warn("重入执行结束,respond={},transmitModel={}", respond, transmitModel);

            engineApiResponse.setEngineResult(resultEnum);
            engineApiResponse.setResult(resultEnum.getResult());
            Map<String, Object> out = PipelineContextHolder.getOut();
            //更新上下文对象里的决策流输出，消费者并不能通过PipelineContextHolder获取，只能从PipelineContext直接取
            PipelineContextHolder.updateOutput(out);
            PipelineContextHolder.setRespond(respond);
            engineApiResponse.setOut(out);
            engineApiResponse.setBranchRejectInfo(PipelineContextHolder.getBranchRejectInfo());
            engineApiResponse.setHitResultList(PipelineContextHolder.getHitResultList());
            engineApiResponse.setRespond(respond);

            return engineApiResponse;

        } catch (Exception e) {
            log.error("重入接口请求异常,error={}", e.getMessage(), e);
            engineApiResponse.setEngineResult(EngineResultEnum.EXCEPTION_END);
            Cat.logError(e);
            return engineApiResponse;
        }
    }


}
